﻿#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>



//<1>原码，反码，补码
//int main()
//{
//	int a = 1;//a是int类型，占4个字节，32比特位（一个字节等于8个比特位）
//	//原码：00000000000000000000000000000001
//	//反码：00000000000000000000000000000001
//	//补码：00000000000000000000000000000001
//		//当符号为正时，原码，反码，补码相同
//	//注：最高位为符号位，其他都是数值位，符号为正时，最高位为0
//	int b = -1;//-1也是int类型，32个bit位
//	//原码：10000000000000000000000000000001		//符号为负时，最高位为1
//	//反码：11111111111111111111111111111110		//符号为负时，反码就是原码取反除了符号位
//	//补码：11111111111111111111111111111111		//符号为负时，补码就是反码+1
//	int c = 1 - 1;//本质上计算机计算方法是1+（-1）
//	//计算机计算时都是补码计算，而不是原码计算
//	//1原码：00000000000000000000000000000001
//	//-1原码：10000000000000000000000000000001
//	//相加后：10000000000000000000000000000010
//	//相加后还变成了-2，所以计算机不采用原码计算，会计算错误
//
//	//1补码：00000000000000000000000000000001
//	//-1补码：11111111111111111111111111111111
//	//相加后：100000000000000000000000000000000（33位了）
//		//	 000000000000000000000000000000000（33去掉1位，因为int类型只有32个bit位）
//	//这时候才会相加正确
//	//注：计算机进行计算时用补码，我们看数据时用的是原码
//	return 0;
//}



//<2>左移操作符
//int main()
//{
//	int a = 10;
//	//符号位为正，原码和补码一样
//	//00000000000000000000000000001010（原码）
//	//00000000000000000000000000001010（补码）
//	int b = a << 1;//左移操作，移动1位
//	//00000000000000000000000000001010（补码）（进行左移）
//	//00000000000000000000000000010100（补码）（左移操作：（都是对补码进行操作，用原码来观察）左边去一位，右边添0）
//	//00000000000000000000000000010100（原码）
//	//会发现：原来有1的地方，2^1,2^3变成了2^2和2^4，每个加数都扩大了两倍，整体上扩大了两倍
//
//	//如果符号位为负的呢
//
//	int c = -10;
//	//原码：10000000000000000000000000001010
//	//反码：11111111111111111111111111110101
//	//补码：11111111111111111111111111110110
//	int d = c << 1;
//	//补码：11111111111111111111111111101100
//	//原码：10000000000000000000000000010100
//	//会发现：原码中原来有1的地方，都向前进了1位，2^1,2^3变成了2^2,2^4,每个加数都扩大了两倍，整体上扩大了两倍，并且符号位没变
//	//这时候会发现：不论正负，左移都会扩大成数的两倍，说明左移n位，会扩大2^n倍
//	//计算时，也会发现反码和补码的意义重大，取反符号位才没有被影响，+1才能使数字准确
//	return 0;
//}



//<3>右移操作符
			//右移操作分为：逻辑右移和算术右移
//int main()
//{
//	int a = 10;
//	//符号位为正，原码和补码相同
//	//00000000000000000000000000001010（补码）
//
//	int b = a >> 1;//（右移一位）
//	//00000000000000000000000000000101（补码）（右边去掉一位，左边加上0）（逻辑右移）
//	//00000000000000000000000000000101（原码）
//	//00000000000000000000000000000101（补码）（右边去掉一位，左边加上原来该值的符号位）（算术右移）
//	//00000000000000000000000000000101（原码）
//	//会发现：右移一位，有1的地方，2^1，2^3变成了2^0，2^2，整体上缩小了两倍
//
//	int c = -10;
//	//符号位为负
//	//原码：10000000000000000000000000001010
//	//反码：11111111111111111111111111110101
//	//补码：11111111111111111111111111110110
//	int d = c >> 1;//（右移一位）
//	//01111111111111111111111111111011（补码）（逻辑右移，右边去掉一位，左边直接加0）
//	//00000000000000000000000000000101（原码）
//	//11111111111111111111111111111011（补码）（算术右移，右边去掉一位，左边加上原来该值的符号位）
//	//10000000000000000000000000000101（原码）、
//	//会发现：整体上也缩小了两倍，说明不论正负，右移n位会缩小2^n倍
//	//逻辑右移的结果改变了原来值的正负，说明逻辑右移在负数情况下是不合逻辑的，并且我们现在编译器都是算术右移
//	return 0;
//}	



//<4>按位与 &
//int main()
//{
//	int a = 4;
//	//补码：00000000000000000000000000000100  4的补码
//
//	int b = -7;
//	//补码：11111111111111111111111111111001  -7的补码
//	int c = a & b;//&（按位与使用补码进行操作，使用原码进行观察）：对应二进制位有0就是0，两个同时为1才是1
//	//补码：00000000000000000000000000000100  4的补码
//	//补码：11111111111111111111111111111001  -7的补码
//	//结果：00000000000000000000000000000000  0
//  //00000000000000000000000000000000中以0开头，符号位为正，补码和原码相同
//	printf("%d ", c);
//	return 0;
//}



//<5>按位或 |
//int main()
//{
//	int a = 4;
//	//补码：00000000000000000000000000000100  4的补码
//
//	int b = -7;
//	//补码：11111111111111111111111111111001  -7的补码
//	int c = a | b;//|（按位或使用补码进行操作，使用原码进行观察）：对应二进制位有1就是1，两个同时为0才是0
//	//补码：00000000000000000000000000000100  4的补码
//	//补码：11111111111111111111111111111001  -7的补码
//	//结果：11111111111111111111111111111101  符号位为负，补码和原码不同
//	//结果：10000000000000000000000000000011  原码：-3  
//	printf("%d ", c);
//	return 0;
//}
		//注：按位与和按位或本质上都是对1（真）的追求，与是两者（对应二进制位）同时为真才能是真，或是两者只要有1个为真就为真



//<6>按位异或 ^
//int main()
//{
//	int a = 4;
//	//补码：00000000000000000000000000000100  4的补码
//
//	int b = -7;
//	//补码：11111111111111111111111111111001  -7的补码
//	int c = a ^ b;//^（按位异或使用补码进行操作，使用原码进行观察）：对应二进制位相同为0，相异为1
//	//补码：00000000000000000000000000000100  4的补码
//	//补码：11111111111111111111111111111001  -7的补码
//	//结果：11111111111111111111111111111101  补码
//	//结果：10000000000000000000000000000011  原码：-3
//	// 注：在计算后，要转换成原码进行观察，	补码为正，直接为原码，补码为负，取反（符号位不取反），+1  
//	printf("%d ", c);
//	return 0;
//}



//<7>按位取反 ~
//int main()
//{
//	int a = 0;
//	//补码：00000000000000000000000000000000  a补码
//	int b = ~a;//按位取反 ~：每一位都要取反，包括符号位
//	//补码：00000000000000000000000000000000  a补码
//	//补码：11111111111111111111111111111111  b补码
//	//原码：10000000000000000000000000000001  b原码：-1
//	printf("%d ", b);
//
//	return 0;
//}
			//注：反码取反和按位取反是不同的：反码取反除符号位都要取反，按位取反包括符号位全部都要取反



//<8>实现两个整数的交换
//int main()
//{
//	int a = 3;
//	int b = 5;
//	int c = 0;//借助空瓶来实现两个整数交换
//	printf("打印前：a = %d b = %d\n", a, b);
//	c = a;
//	a = b;
//	b = c;
//	printf("打印后：a = %d b = %d", a, b);
//	return 0;
//}

//不能创建临时变量（第三个变量），实现两个整数的交换
//int main()
//{
//	int a = 3;
//	int b = 5;
//	printf("打印前：a = %d b = %d\n", a, b);
//	a = a + b;//a=8 b=5
//	b = a - b;//b=3 a=8
//	a = a - b;//b=3 a=5
//				//代码不够好，因为涉及进位，当a，b较大时，容易溢出
//				//比如说：a可以等于30000，b也可以等于30000（整型最大范围为32767），但是a+b就超出整型范围了
//	printf("打印后：a = %d b = %d", a, b);
//	return 0;
//}

//（2）方案2 利用按位异或进行计算
//按位异或使用的特殊场景
//a ^ a = 0
//a ^ 0 = a
//利用这两个公式就可以解决一些特殊问题
//int main()
//{
//	int a = 3;
//	int b = 5;
//	printf("打印前：a = %d b = %d", a, b);
//	a = a ^ b;
//	b = a ^ b;//因为a=a^b，所以b=a^b^b=a^0=a，所以b = a
//	a = a ^ b;//因为b=a^b，所以a=a^a^b=0^b=b，所以a = b
//	//所以利用按位异或的公式进行了两个整数的转换，并且没有进位进行转换，一般不会出现溢出的问题，但代码可读性差，效率也差，并且只能用于整型
//		//注：按位异或的公式是符合结合律的，即a^b^a=a^a^b
//	printf("打印前：a = %d b = %d", a, b);
//
//	return 0;
//}



//<9>求⼀个整数存储在内存中的⼆进制中1的个数
			//就是求补码中1的个数
//int count_one_of_bit(unsigned int n)//把补码进行无符号处理，当成无符号数处理，即使n=负数，也不影响计算
//{
//	int count = 0;
//	while (n)
//	{
//
//		if (n % 2 == 1)//二进制在%，/等操作符实现上与十进制一样
//		{			   //选择%2，/2是因为进制，二进制最大值为1，要求补码中的个数，就要在二进制中实现，只有二进制才能在补码中做到把最后一位完整变成余数或者完整去掉最后一位
//			count++;	//就像十进制中，十进制最大值为9，只有%10，/10才能把一个数的临时的最后一位进行完整取模，或是完整去掉最后一位
//		}
//		n /= 2;//要放在if语句的外面，不然只有n对2的操作余数为1时才会去掉最后一位，如果余数不为1
//		//比如最后一位为0，0%2=0，就不能进入if语句从而去掉0，但整个数又不为0，就会陷入死循环
//
//		//注：%，/循环操作的实现：当操作数剩下最后一位时，且该位比取模的数小时，该位就会成为余数，当该位比除数小时，就只会成为0
//				//然后0传到循环限制条件中，停止循环
//	}
//	return count;
//}
//int main()
//{
//	int n = 0;
//	scanf("%d", &n);
//	int ret = count_one_of_bit(n);
//	printf("%d ", ret);
//
//	return 0;
//}

//int count_one_of_bit(unsigned int n)
//{
//	int count = 0;
//	while (n)
//	{
//
//		if (n & 1 == 1)//利用&进行操作，任何数&1得出的结果只会是0或1，取决于那个任何数补码的最后一位是0还是1
//						//因为1的补码表示00000000000000001，前面都是0，0&任何数都是0，只剩最后的1才有机会为1
//		{			   
//			count++;	
//		}
//		n /= 2;
//
//	}
//	return count;
//}
//int main()
//{
//	int n = 0;
//	scanf("%d", &n);
//	int ret = count_one_of_bit(n);
//	printf("%d ", ret);
//
//	return 0;
//}

//int count_one_of_bit(unsigned int n)
//{
//	int count = 0;
//	int i = 0;
//	for (i = 0; i < 32; i++)//最后一位不用移，只需要检测就可以
//	{
//		if (((n >> i)& 1) == 1)
//		{
//			count++;
//		}
//	}
//	return count;
//}
//int main()
//{
//	int n = 0;
//	scanf("%d", &n);
//	int ret = count_one_of_bit(n);
//	printf("%d ", ret);
//
//	return 0;
//}
//
//int count_one_of_bit(unsigned int n)
//{
//	int count = 0;
//	while (n)
//	{
//		count++;//进来就count一次因为n = n & (n - 1)只顾虑1，而不顾虑0
//		n = n & (n - 1);//利用&和相邻两个数的特点，让在二进制中的n最右边的1消失，原理自己书面计算
//	}					//n = 表示n又会变化，从而实现循环
//	return count;
//}
//int main()
//{
//	int n = 0;
//	scanf("%d", &n);
//	int ret = count_one_of_bit(n);
//	printf("%d ", ret);
//
//	return 0;
//}



//<10>⼆进制位置0或者置1
//编写代码将13⼆进制序列的第5位修改为1，然后再改回0
//13的2进制序列： 00000000000000000000000000001101
//将第5位置为1后：00000000000000000000000000011101
//将第5位再置为0：00000000000000000000000000001101
//int main()
//{
//	int a = 13;
//	a = a | (1 << 4);//先把1（00000000000000000000000000000001）右移4个位，再按位或，而不是改变a的移位
//	printf("改为1后：%d", a);
//	a = a & ~(1 << 4);//先把1（00000000000000000000000000000001）右移4个位，再取反，就能得到要&的值
//	printf("改为0后：%d", a);
//
//
//	return 0;
//}